/*  SPDX-License-Identifier: GPL-2.0-or-later */
/*!********************************************************************

  Audacity: A Digital Audio Editor

  CloudProjectExtension.h

  Dmitry Vedenko

**********************************************************************/
#pragma once

#include <atomic>
#include <cstdint>
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <string>
#include <string_view>
#include <vector>

#include "ClientData.h"
#include "Observer.h"

#include "CloudSyncError.h"
#include "CloudSyncDTO.h"
#include "ProjectUploadOperation.h"

class AudacityProject;
class ProjectSerializer;

enum class AudiocomTrace;

namespace audacity::cloud::audiocom::sync {
class ProjectUploadOperation;

enum class ProjectSyncStatus
{
    Local,
    Unsynced,
    Synced,
    Failed,
    Syncing
};

struct CLOUD_AUDIOCOM_API CloudStatusChangedMessage final
{
    AudiocomTrace audiocomTrace;
    ProjectSyncStatus Status { ProjectSyncStatus::Local };
    double Progress {};
    std::optional<CloudSyncError> Error {};

    bool IsSyncing() const noexcept;
};

class CLOUD_AUDIOCOM_API ProjectCloudExtension final : public ClientData::Base
{
public:
    explicit ProjectCloudExtension(AudacityProject& project);
    ~ProjectCloudExtension() override;

    static ProjectCloudExtension& Get(AudacityProject& project);
    static const ProjectCloudExtension& Get(const AudacityProject& project);

    bool IsCloudProject() const;

    void OnLoad();

    //! This method is called from the UI thread
    void OnSyncStarted();
    //! This method is called from the UI thread
    void OnSyncResumed(
        std::shared_ptr<ProjectUploadOperation> uploadOperation, int64_t missingBlocksCount, bool needsProjectUpload);
    //! This method is called from the UI thread
    void OnUploadOperationCreated(
        std::shared_ptr<ProjectUploadOperation> uploadOperation);
    //! This method is called not from the UI thread
    void OnBlocksHashed(ProjectUploadOperation& uploadOperation);
    //! This method is called from the network thread
    void OnSnapshotCreated(
        const ProjectUploadOperation& uploadOperation, const CreateSnapshotResponse& response);
    //! This method is called from the network thread
    void OnProjectDataUploaded(const ProjectUploadOperation& uploadOperation);
    //! This method is called from the network thread
    void OnBlockUploaded(
        const ProjectUploadOperation& uploadOperation, std::string_view blockID, bool successful);
    //! This method is called from any thread
    void OnSyncCompleted(
        const ProjectUploadOperation* uploadOperation, std::optional<CloudSyncError> error, AudiocomTrace trace);

    void AbortLastUploadOperation();

    void CancelSync();

    bool IsSyncing() const;

    std::string GetCloudProjectId() const;
    std::string GetSnapshotId() const;

    void OnUpdateSaved(const ProjectSerializer& serializer);

    std::weak_ptr<AudacityProject> GetProject() const;

    int64_t GetSavesCount() const;

    Observer::Subscription SubscribeStatusChanged(
        std::function<void(const CloudStatusChangedMessage&)> callback, bool onUIThread);

    std::string GetCloudProjectPage(AudiocomTrace) const;

    bool IsBlockLocked(int64_t blockID) const;

    ProjectSyncStatus GetCurrentSyncStatus() const;

    bool IsFirstSyncDialogShown() const;
    void SetFirstSyncDialogShown(bool shown = true);

private:
    struct UploadQueueElement;
    struct CloudStatusChangedNotifier;

    void UpdateIdFromDatabase();

    void UnsafeUpdateProgress();
    void Publish(CloudStatusChangedMessage cloudStatus, bool canMerge);

    void MarkProjectSynced(bool success);

    UploadQueueElement*
    UnsafeFindUploadQueueElement(const ProjectUploadOperation& uploadOperation);
    const UploadQueueElement* UnsafeFindUploadQueueElement(
        const ProjectUploadOperation& uploadOperation) const;

    void OnProjectPathChanged();

    AudacityProject& mProject;

    Observer::Subscription mProjectPathChangedSubscription;

    mutable std::mutex mIdentifiersMutex;
    std::string mProjectId;
    std::string mSnapshotId;

    std::mutex mUploadQueueMutex;
    std::vector<std::shared_ptr<UploadQueueElement>> mUploadQueue;

    mutable std::mutex mStatusMutex;
    CloudStatusChangedMessage mLastStatus;

    std::unique_ptr<CloudStatusChangedNotifier> mAsyncStateNotifier;
    std::unique_ptr<CloudStatusChangedNotifier> mUIStateNotifier;
    std::atomic<bool> mUINotificationPending { false };
}; // class ProjectCloudExtension
} // namespace audacity::cloud::audiocom::sync
